﻿using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using UnityEngine.Graphics.Texture.Ijl15;

namespace UnityEngine.Graphics.Texture
{
    public static class BlpConverter
    {
        /// <summary>
        /// load a image stored in blp file format
        /// </summary>
        /// <param name="blpStream">input blp file stream</param>
        /// <returns>loaded image</returns>
        /// <exception cref="System.ArgumentNullException">blpStream is null</exception>
        /// <exception cref="System.ArgumentException">blpStream is not readable or already closed.</exception>
        /// <exception cref="System.IO.IOException">blp file is corrupted, or file format is not supported</exception>
        /// <exception cref="Deerchao.BlpConvert.BlpConverterException">intel jpeg library reported a error</exception>
        /// <exception cref="System.Runtime.InteropServices.ExternalException">unhandled excetipon thrown by unmanaged code</exception>
        public static Bitmap GetImageFromBlpFile(Stream blpStream)
        {
            if (blpStream == null)
                throw new ArgumentNullException("blpStream should not be null", "blpStream");

            BinaryReader reader = new BinaryReader(blpStream);

            int magic = reader.ReadInt32();
            int isPaletted = reader.ReadInt32();
            int hasAlpha = reader.ReadInt32();
            int width = reader.ReadInt32();
            int height = reader.ReadInt32();
            int type = reader.ReadInt32();
            int subType = reader.ReadInt32();

            int[] offsets = new int[16];
            int[] sizes = new int[16];

            for (int i = 0; i < 16; i++)
                offsets[i] = reader.ReadInt32();
            for (int i = 0; i < 16; i++)
                sizes[i] = reader.ReadInt32();

            if (isPaletted == 0)
            {
                int headerSize = reader.ReadInt32();

                //skip null bytes
                while (reader.ReadByte() == 0) ;
                reader.BaseStream.Position--;

                MemoryStream temp = new MemoryStream();
                byte[] header = reader.ReadBytes(headerSize);
                temp.Write(header, 0, header.Length);

                reader.BaseStream.Position = offsets[0];
                byte[] content = reader.ReadBytes(sizes[0]);
                temp.Write(content, 0, content.Length);

                Stream imageData = TransformBlpJpegColorSpace(temp.ToArray());
                return new Bitmap(imageData);
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// transform a jpg image in blp color space to a bmp image in normal color space
        /// </summary>
        /// <param name="jpgData">jpg image data in blp color space</param>
        /// <returns>bmp image in normal color space</returns>
        /// <exception cref="System.ArgumentNullException">jpgData is null</exception>
        /// <exception cref="Deerchao.BlpConvert.BlpConverterException">intel jpeg library reported a error</exception>
        /// <exception cref="System.Runtime.InteropServices.ExternalException">unhandled excetipon thrown by unmanaged code</exception>
        private unsafe static Stream TransformBlpJpegColorSpace(byte[] jpgData)
        {
            if (jpgData == null)
                throw new ArgumentNullException("jpgData should not be null", "jpgData");

            int dibWidth = 0;
            int dibHeight = 0;
            int dibChannels = 0;
            byte[] dibData = GetDibDataFromJpegFileData(jpgData, out dibWidth, out dibHeight, out dibChannels);

            MemoryStream result = new MemoryStream();

            int pad_bytes = NativeMethods.IJL_DIB_PAD_BYTES(dibWidth, dibChannels);
            int bwidth = dibWidth * dibChannels;
            int width_step = bwidth + pad_bytes;
            int img_size = width_step * dibHeight;

            BITMAPFILEHEADER fileHeader = new BITMAPFILEHEADER();
            BITMAPINFOHEADER bmpHeader = new BITMAPINFOHEADER();

            int file_size = img_size + Marshal.SizeOf(fileHeader) + Marshal.SizeOf(bmpHeader);

            fileHeader.bfType = BitConverter.ToUInt16(new byte[] { (byte)'B', (byte)'M' }, 0);
            fileHeader.bfSize = (uint)file_size;
            fileHeader.bfReserved1 = 0;
            fileHeader.bfReserved2 = 0;
            fileHeader.bfOffBits = (uint)(Marshal.SizeOf(fileHeader) + Marshal.SizeOf(bmpHeader));

            //bmpHeader.biSize = (uint)Marshal.SizeOf(bmpHeader);
            bmpHeader.Init();
            bmpHeader.biWidth = dibWidth;
            bmpHeader.biHeight = -dibHeight;
            bmpHeader.biCompression = NativeMethods.BI_RGB;
            bmpHeader.biPlanes = 1;

            switch (dibChannels)
            {
                case 3:
                    bmpHeader.biBitCount = 24;
                    break;
                case 4:
                    bmpHeader.biBitCount = 32;
                    break;
                default:
                    goto exit;
            }

            bmpHeader.biCompression = NativeMethods.BI_RGB;
            bmpHeader.biSizeImage = (uint)img_size;
            bmpHeader.biXPelsPerMeter = 0;
            bmpHeader.biYPelsPerMeter = 0;
            bmpHeader.biClrUsed = 0;
            bmpHeader.biClrImportant = 0;

            //copy file header
            BITMAPFILEHEADER* t = &fileHeader;
            byte* s = (byte*)t;

            for (int i = 0; i < Marshal.SizeOf(fileHeader); i++)
                result.WriteByte(s[i]);

            //copy bmp header
            s = (byte*)&bmpHeader;
            for (int i = 0; i < Marshal.SizeOf(bmpHeader); i++)
                result.WriteByte(s[i]);

            //copy dibData
            result.Write(dibData, 0, img_size);

        exit:
            return result;
        }

        /// <summary>
        /// convert jpeg in blp color space into dib image data in normal color space
        /// </summary>
        /// <param name="jpgData">jpeg file data in blp color space</param>
        /// <param name="dibWidth">[Out] converted image width</param>
        /// <param name="dibHeight">[Out] converted image height</param>
        /// <param name="dibChannels">[Out] converted image channel count</param>
        /// <returns>converted dib image data</returns>
        /// <exception cref="System.ArgumentNullException">jpgData is null</exception>
        /// <exception cref="Deerchao.BlpConvert.BlpConverterExcetion">intel jpeg library reported a error</exception>
        /// <exception cref="System.Runtime.InteropServices.ExternalException">unhandled excetipon thrown by unmanaged code</exception>
        private unsafe static byte[] GetDibDataFromJpegFileData(byte[] jpgData, out int dibWidth, out int dibHeight, out int dibChannels)
        {
            if (jpgData == null)
                throw new ArgumentNullException("jpgData should not be null.", "jpgData");

            dibWidth = 0;
            dibHeight = 0;
            dibChannels = 0;

            JPEG_CORE_PROPERTIES image = new JPEG_CORE_PROPERTIES();
            byte[] imageData = null;
            fixed (byte* pIn = jpgData)
            {
                try
                {
                    IJLERR error = NativeMethods.ijlInit(ref image);
                    if (error != IJLERR.IJL_OK)
                        throw new BlpConverterException(error);

                    image.JPGBytes = new IntPtr(pIn);
                    image.JPGSizeBytes = jpgData.Length;

                    error = NativeMethods.ijlRead(ref image, IJLIOTYPE.IJL_JBUFF_READPARAMS);
                    if (error != IJLERR.IJL_OK)
                        throw new BlpConverterException(error);

                    switch (image.JPGChannels)
                    {
                        case 1:
                            image.JPGColor = IJL_COLOR.IJL_G;
                            image.DIBChannels = 3;
                            image.DIBColor = IJL_COLOR.IJL_BGR;
                            break;

                        case 3:
                            image.JPGColor = IJL_COLOR.IJL_YCBCR;
                            image.DIBChannels = 3;
                            image.DIBColor = IJL_COLOR.IJL_BGR;
                            break;

                        default:
                            image.DIBColor = IJL_COLOR.IJL_OTHER;
                            image.JPGColor = IJL_COLOR.IJL_OTHER;
                            image.DIBChannels = image.JPGChannels;
                            break;
                    }

                    image.DIBWidth = image.JPGWidth;
                    image.DIBHeight = image.JPGHeight;
                    image.DIBPadBytes = NativeMethods.IJL_DIB_PAD_BYTES(image.DIBWidth, image.DIBChannels);

                    int imageSize = (image.DIBWidth * image.DIBChannels + image.DIBPadBytes) * image.DIBHeight;

                    imageData = new byte[imageSize];

                    fixed (byte* pImageData = imageData)
                    {
                        image.DIBBytes = new IntPtr(pImageData);

                        error = NativeMethods.ijlRead(ref image, IJLIOTYPE.IJL_JBUFF_READWHOLEIMAGE);
                        if (error != IJLERR.IJL_OK)
                            throw new BlpConverterException(error);

                        error = NativeMethods.ijlFree(ref image);
                        if (error != IJLERR.IJL_OK)
                            throw new BlpConverterException(error);

                        if (image.DIBColor == IJL_COLOR.IJL_RGBA_FPX)
                            RGBA_FPX_to_BGRA(pImageData, image.DIBWidth, image.DIBHeight);
                    }
                }
                finally
                {
                    NativeMethods.ijlFree(ref image);
                }

                dibWidth = image.DIBWidth;
                dibHeight = image.DIBHeight;
                dibChannels = image.DIBChannels;

                return imageData;
            }
        }

        private unsafe static void RGBA_FPX_to_BGRA(byte* data, int width, int height)
        {
            int i;
            int j;
            int pad;
            int line_width;
            byte r, g, b, a;
            byte* ptr;

            ptr = data;
            pad = NativeMethods.IJL_DIB_PAD_BYTES(width, 4);
            line_width = width * 4 + pad;

            for (i = 0; i < height; i++)
            {
                ptr = data + line_width * i;
                for (j = 0; j < width; j++)
                {
                    r = ptr[0];
                    g = ptr[1];
                    b = ptr[2];
                    a = ptr[3];
                    ptr[2] = (byte)((r * a + 1) >> 8);
                    ptr[1] = (byte)((g * a + 1) >> 8);
                    ptr[0] = (byte)((b * a + 1) >> 8);
                    ptr += 4;
                }
            }
        }
    }
}
